package com.tianruan.dc.common.utils;

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.mail.MessagingException;
import java.io.*;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.zip.Adler32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @Author: Dong
 * @Email: dong980514280@gmail.com
 * @Create: 2021-12-23 11:20
 * @PS 阿里云OSS存储对象工具类
 */
@Component
@Slf4j
public class OSSUtils {

    @Qualifier("contractProcessExector")
    @Autowired
    private ThreadPoolTaskExecutor contractProcessExector;

    public static final String ENDPOINT_URL = "https://xxx.oss-cn-guangzhou.aliyuncs.com/";
    /**
     * 阿里云的配置参数
     */
    private static String accessKeyId = null;
    private static String accessKeySecret = null;
    private static String endpoint = null;
    private static String bucketName = null;
    private static String region = null;

    private static String host = null;
    /**
     * 存储在OSS中的前缀名
     */
    private static String file_prefix = null;
    private static OSS ossClient;

    /**
     * 静态块
     */
    static {
//        //初始化AccessKey
//        accessKeyId = PropertiesReader.get("aliyun.AccessKeyID");
//        //初始化AccessKeySecret
//        accessKeySecret = PropertiesReader.get("aliyun.oss.AccessKeySecret");
//        //初始化Endpoint
//        endpoint = PropertiesReader.get("aliyun.oss.EndPoint");
//        //初始化bucketName
//        bucketName = PropertiesReader.get("aliyun.oss.Buckets");
        accessKeyId = "LTAI5t9KuABHcBepDdkH5bVq";
        accessKeySecret = "MC99cWX73puG5fc6h0bfPNIs4ZPR5y";
        endpoint = "oss-cn-shenzhen.aliyuncs.com";
        bucketName = "tr-ipa";
        region = "cn-shenzhen";
        host = "https://" + bucketName + endpoint;

        //初始化前缀
//        file_prefix = PropertiesReader.get("aliyun.oss.prefix");
        file_prefix = "files/";

        ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    }

    /**
     * 私有化构造
     */
    public OSSUtils() {

    }

    /**
     * 获取图片的URL头信息
     *
     * @return 返回url头信息
     */
    private static String getURLHead() {
        //从哪个位置截取
        int cutPoint = endpoint.lastIndexOf('/') + 1;
        //http头
        String head = endpoint.substring(0, cutPoint);
        //服务器地址信息
        String tail = endpoint.substring(cutPoint);
        //返回结果
        return "https://" + bucketName + "." + tail + "/";
    }

    /**
     * 获取存储在服务器上的地址
     *
     * @param oranName 文件名
     * @return 文件URL
     */
    private static String getRealName(String oranName) {
        return getURLHead() + oranName;
    }

    /**
     * 获取一个随机的文件名
     *
     * @param oranName 初始的文件名
     * @return 返回加uuid后的文件名
     */
    private static String getRandomImageName(String oranName) {
        //获取一个uuid 去掉-
        String uuid = RandomUtils.randomBase62(32);
        //查一下是否带路径
        int cutPoint = oranName.lastIndexOf("/") + 1;
        //如果存在路径
        if (cutPoint != 0) {
            //掐头 如果开头是/ 则去掉
            String head = oranName.indexOf("/") == 0 ? oranName.substring(1, cutPoint) : oranName.substring(0, cutPoint);
            //去尾
            String tail = oranName.substring(cutPoint);
            //返回正确的带路径的图片名称
            return file_prefix + head + uuid + tail;
        }
        //不存在 直接返回
        return file_prefix + uuid + oranName;
    }

    /**
     * MultipartFile2File
     *
     * @param multipartFile
     * @return
     */
    private static File transferToFile(MultipartFile multipartFile) {
        //选择用缓冲区来实现这个转换即使用java 创建的临时文件 使用 MultipartFile.transferto()方法 。
        File file = null;
        try {
            //获取文件名
            String originalFilename = multipartFile.getOriginalFilename();
            //获取最后一个"."的位置
            int cutPoint = originalFilename.lastIndexOf(".");
            //获取文件名
            String prefix = originalFilename.substring(0, cutPoint);
            //获取后缀名
            String suffix = originalFilename.substring(cutPoint + 1);
            //创建临时文件
            file = File.createTempFile(RandomUtils.randomBase62(5) + "_" + prefix, suffix);
            //multipartFile2file
            multipartFile.transferTo(file);
            //删除临时文件
            file.deleteOnExit();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return file;
    }

    /**
     * 上传文件流
     *
     * @param oranFileName 上传到服务器上的文件路径和名称
     * @param file         来自本地的文件或者文件流
     */
    public static String uploadFileInputSteam(String oranFileName, MultipartFile file) {
        // <yourObjectName>上传文件到OSS时需要指定包含文件后缀在内的完整路径，例如abc/efg/123.jpg
        String objectName = getRandomImageName(oranFileName);
        objectName = objectName + "/" + file.getOriginalFilename();

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        // 上传文件流
        try (InputStream inputStream = new FileInputStream(transferToFile(file))) {
            //上传到OSS
            ossClient.putObject(bucketName, objectName, inputStream);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        // 关闭OSSClient。
        ossClient.shutdown();

        //返回文件在服务器上的全路径+名称
        return getRealName(objectName);
    }


    /**
     * 上传文件流
     *
     * @param oranFileName 上传到服务器上的文件路径和名称
     * @param file         来自本地的文件或者文件流
     */
    public static String uploadFileInputSteam(String oranFileName, File file) {

        // <yourObjectName>上传文件到OSS时需要指定包含文件后缀在内的完整路径，例如abc/efg/123.jpg
        String objectName = getRandomImageName(oranFileName);

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        // 上传文件流。
        try (InputStream inputStream = new FileInputStream(file);) {
            //上传到OSS
            ossClient.putObject(bucketName, objectName, inputStream);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        // 关闭OSSClient。
        ossClient.shutdown();

        //返回文件在服务器上的全路径+名称
        return getRealName(objectName);
    }

//    // 批量下载
//    public static void batchDownLoadFile(List<Map<String, String>> fileMap, HttpServletRequest request, HttpServletResponse response) {
//        //获取前端传过来的文件id集合
//
//        try {
//            String fileName = "test.zip";
//            // 创建临时文件
//            File zipFile = File.createTempFile("test", ".zip");
//            FileOutputStream f = new FileOutputStream(zipFile);
//            //作用是为任何OutputStream产生校验
//            //第一个参数是制定产生校验和的输出流，第二个参数是指定Checksum的类型 （Adler32（较快）和CRC32两种）
//            CheckedOutputStream csum = new CheckedOutputStream(f, new Adler32());
//            //用于将数据压缩成Zip文件格式
//            ZipOutputStream zos = new ZipOutputStream(csum);
//
//            for (Map<String, String> file : fileMap) {
//                //数据库中获取自己的文件记录
//                OSSObject ossObject = ossClient.getObject(bucketName, file.get("path"));
//                InputStream inputStream = ossObject.getObjectContent();
//                //zip设置文件名
//                zos.putNextEntry(new ZipEntry(new Date().getTime() + file.get("name")));
//                int bytesRead = 0;
//                // 向压缩文件中输出数据
//                while ((bytesRead = inputStream.read()) != -1) {
//                    zos.write(bytesRead);
//                }
//                inputStream.close();
//                // 当前文件写完，定位为写入下一条项目
//                zos.closeEntry();
//            }
//            zos.close();
//            String header = request.getHeader("User-Agent").toUpperCase();
//            if (header.contains("MSIE") || header.contains("TRIDENT") || header.contains("EDGE")) {
//                fileName = URLEncoder.encode(fileName, "utf-8");
//                //IE下载文件名空格变+号问题
//                fileName = fileName.replace("+", "%20");
//            } else {
//                fileName = new String(fileName.getBytes(), "ISO8859-1");
//            }
//            response.reset();
//            response.setContentType("text/plain");
//            response.setContentType("application/octet-stream; charset=utf-8");
//            response.setHeader("Location", fileName);
//            response.setHeader("Cache-Control", "max-age=0");
//            response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
//
//            FileInputStream fis = new FileInputStream(zipFile);
//            BufferedInputStream buff = new BufferedInputStream(fis);
//            BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
//            byte[] car = new byte[1024];
//            int l = 0;
//            while (l < zipFile.length()) {
//                int j = buff.read(car, 0, 1024);
//                l += j;
//                out.write(car, 0, j);
//            }
//            // 关闭流
//            fis.close();
//            buff.close();
//            out.close();
//
//            // 删除临时文件
//            zipFile.delete();
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//    }

    public FileInputStream downLoadFile(String path) throws IOException {
        boolean objectExist = doesObjectExist(path);
        if (objectExist) {
            OSSObject ossObject = ossClient.getObject(bucketName, path);
            InputStream inputStream = ossObject.getObjectContent();

            File tempFile = File.createTempFile("temp" + IdGen.uuid(), ".tmp");
            tempFile.deleteOnExit();
            try (FileOutputStream outputStream = new FileOutputStream(tempFile)) {
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
            }
            return new FileInputStream(tempFile);
        }
        return null;
    }

    // 批量下载
    public OutputStream batchDownLoadFile(List<Map<String, String>> fileMap, String folderName, String fileName, Map<String, String> emails) {
        //获取前端传过来的文件id集合

        try {
            // 创建临时文件
            File zipFile = File.createTempFile(fileName + "_", ".zip");
            FileOutputStream f = new FileOutputStream(zipFile);
            //作用是为任何OutputStream产生校验
            //第一个参数是制定产生校验和的输出流，第二个参数是指定Checksum的类型 （Adler32（较快）和CRC32两种）
            CheckedOutputStream csum = new CheckedOutputStream(f, new Adler32());
            //用于将数据压缩成Zip文件格式
            ZipOutputStream zos = new ZipOutputStream(csum);

            for (Map<String, String> file : fileMap) {
                //数据库中获取自己的文件记录
                boolean objectExist = doesObjectExist(file.get("path"));
                if (objectExist) {
                    OSSObject ossObject = ossClient.getObject(bucketName, file.get("path"));
                    InputStream inputStream = ossObject.getObjectContent();

                    //zip设置文件名
                    zos.putNextEntry(new ZipEntry(folderName + File.separator + new Date().getTime()  + "_" + file.get("name") ));
                    int bytesRead = 0;
                    // 向压缩文件中输出数据
                    while ((bytesRead = inputStream.read()) != -1) {
                        zos.write(bytesRead);
                    }
                    inputStream.close();
                    // 当前文件写完，定位为写入下一条项目
                    zos.closeEntry();
                }
            }
            zos.close();
            zos.flush();


            //附件集
            List<File> fileList=new ArrayList<>();
            fileList.add(zipFile);


            String location = test(
                    "dc/" + RandomUtils.randomBase62(32) + "/互联网+项目计划书.zip", zipFile);
            log.info("oss下载地址 {}", location);
            //发送邮件
            try {
                MailUtils.sendEmil(null, null, emails, null,"互联网+项目文件","互联网+项目下载地址为：" + location, null, null);
                System.out.println("发送完成");
            } catch (MessagingException e) {
                e.printStackTrace();
            }

            // 删除临时文件
            zipFile.delete();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    // 批量下载
    public void batchDownLoadFile2Zip(List<Map<String, String>> fileMap, String folderName, String fileName) {
        //获取前端传过来的文件id集合

        try {
            // 创建临时文件
            File zipFile = File.createTempFile(fileName + "_", ".zip");
            FileOutputStream f = new FileOutputStream(zipFile);
            //作用是为任何OutputStream产生校验
            //第一个参数是制定产生校验和的输出流，第二个参数是指定Checksum的类型 （Adler32（较快）和CRC32两种）
            CheckedOutputStream csum = new CheckedOutputStream(f, new Adler32());
            //用于将数据压缩成Zip文件格式
            ZipOutputStream zos = new ZipOutputStream(csum);

            for (Map<String, String> file : fileMap) {
                //数据库中获取自己的文件记录
                boolean objectExist = doesObjectExist(file.get("path"));
                if (objectExist) {
                    OSSObject ossObject = ossClient.getObject(bucketName, file.get("path"));
                    InputStream inputStream = ossObject.getObjectContent();

                    //zip设置文件名
                    zos.putNextEntry(new ZipEntry(folderName + File.separator + new Date().getTime()  + "_" + file.get("name") ));
                    int bytesRead = 0;
                    // 向压缩文件中输出数据
                    while ((bytesRead = inputStream.read()) != -1) {
                        zos.write(bytesRead);
                    }
                    inputStream.close();
                    // 当前文件写完，定位为写入下一条项目
                    zos.closeEntry();
                }
            }
            zos.close();
            zos.flush();

            String location = test(
                    "dc/" + RandomUtils.randomBase62(32) + "/" + folderName + ".zip", zipFile);
            log.info("oss下载地址 {}", location);

            // 删除临时文件
            zipFile.delete();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 判断oss文件是否存在
     * @param objectName
     * @return
     */
    public static Boolean doesObjectExist(String objectName) {
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        try {
            // 判断文件是否存在。如果返回值为true，则文件存在，否则存储空间或者文件不存在。
            // 设置是否进行重定向或者镜像回源。默认值为true，表示忽略302重定向和镜像回源；如果设置isINoss为false，则进行302重定向或者镜像回源。
            //boolean isINoss = true;
            return ossClient.doesObjectExist(bucketName, objectName);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
            return false;
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
            return false;
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    PartETag getUploadPartETag(String objectName, String bucketName, String uploadId,
                               InputStream instream, Long curPartSize,Integer partNum,
                               OSS ossClient, CountDownLatch countDownLatch){
        long before = System.currentTimeMillis();
        UploadPartRequest uploadPartRequest = null;
        try {
            log.debug("分片文件上传线程： {}",Thread.currentThread().getName());
            uploadPartRequest = new UploadPartRequest();
            uploadPartRequest.setBucketName(bucketName);
            uploadPartRequest.setKey(objectName);
            uploadPartRequest.setUploadId(uploadId);
            uploadPartRequest.setInputStream(instream);
            // 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100KB。
            uploadPartRequest.setPartSize(curPartSize);
            // 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出这个范围，OSS将返回InvalidArgument的错误码。
            uploadPartRequest.setPartNumber(partNum);
            // 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
            UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
            // 每次上传分片之后，OSS的返回结果会包含一个PartETag。PartETag将被保存到partETags中。
            log.debug("getPartETag  ::{}" ,uploadPartResult.getPartETag().getETag());
            return uploadPartResult.getPartETag();
        }finally {
            countDownLatch.countDown();
            log.debug("线程： {}  执行完毕, 等待线程数 ：{}, 消耗时间： {}",
                    Thread.currentThread().getName(),countDownLatch.getCount(),
                    ((System.currentTimeMillis()-before)/1000)+"s");
        }
    }

    public String test(String objectName, File file) {
        Long before = System.currentTimeMillis();

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        // 创建InitiateMultipartUploadRequest对象。
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, objectName);

        // 初始化分片。
        InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
        // 返回uploadId，它是分片上传事件的唯一标识，您可以根据这个ID来发起相关的操作，如取消分片上传、查询分片上传等。
        String uploadId = upresult.getUploadId();

        // partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
        List<PartETag> partETags =  new ArrayList<>();
        // 计算文件有多少个分片 15MB
        final long partSize = 2 * 1024 * 1024L;
        long fileLength = file.length();
        int partCount = (int) (fileLength / partSize);
        if (fileLength % partSize != 0) {
            partCount++;
        }
        // 遍历分片上传。
        log.info("分片数量  {}",partCount);
        List<Future<PartETag>> futureList = Collections.synchronizedList(new ArrayList());
        CountDownLatch countDownLatch = new CountDownLatch(partCount);
        for (int i = 0; i < partCount; i++) {
            long startPos = i * partSize;
            long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
            InputStream instream = null;
            try {
                instream = new FileInputStream(file);
            }  catch (IOException e) {
                e.printStackTrace();
            }
            // 跳过已经上传的分片。
            try {
                instream.skip(startPos);
            } catch (IOException e) {
                e.printStackTrace();
            }
            int finalI = i;
            InputStream finalInstream = instream;
            Future<PartETag> partETagFuture = contractProcessExector.submit(() ->
                    getUploadPartETag(objectName, bucketName, uploadId, finalInstream, curPartSize, finalI + 1, ossClient, countDownLatch));
            futureList.add(partETagFuture);
        }
        try {
            countDownLatch.await();
            for (Future<PartETag> tagFuture : futureList) {
                partETags.add(tagFuture.get());
            }
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        // 创建CompleteMultipartUploadRequest对象。
        List<PartETag> collect = partETags.stream().sorted(Comparator.comparing(PartETag::getPartNumber)).collect(Collectors.toList());
        // 在执行完成分片上传操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个完整的文件。
        log.info("文件开始合并");
        CompleteMultipartUploadRequest completeMultipartUploadRequest =
                new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, collect);

        // 如果需要在完成文件上传的同时设置文件访问权限，请参考以下示例代码。
        completeMultipartUploadRequest.setObjectACL(CannedAccessControlList.PublicRead);
        // 完成上传。
        CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);

        // 关闭OSSClient。
        ossClient.shutdown();
        log.info("消耗总时间：  {}",((System.currentTimeMillis()-before)/1000)+"s");

        return completeMultipartUploadResult.getLocation();
    }


}